package com.xucia.jsponic.data;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.SoftReference;
import java.security.Principal;
import java.security.acl.Acl;
import java.security.acl.Group;
import java.security.acl.Permission;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.Map.Entry;

import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.BaseFunction;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.IdScriptableObject;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import com.xucia.jsponic.JsponicRuntime;
import com.xucia.jsponic.datasource.ChangeableData;
import com.xucia.jsponic.datasource.NewObjectPersister;
import com.xucia.jsponic.datasource.PersistableInitializer;
import com.xucia.jsponic.datasource.QueriableDataSource;
import com.xucia.jsponic.datasource.ReferenceAware;
import com.xucia.jsponic.datasource.ReferenceAwareDataSource;
import com.xucia.jsponic.datasource.WritableDataSource;
import com.xucia.jsponic.remote.ClientConnection;
import com.xucia.jsponic.rpc.RPCall;
import com.xucia.jsponic.rpc.RemotelyExecutable;
import com.xucia.jsponic.security.JavaScriptCodingPermission;
import com.xucia.jsponic.security.ObjectAccessDeniedException;
import com.xucia.jsponic.security.PermissionLevel;
import com.xucia.jsponic.security.SystemAcls;
import com.xucia.jsponic.security.UserSecurity;
import com.xucia.jsponic.security.User.PriviledgedUser;
/**
 * This class add capabilites to Rhino scriptable objects for persisting data
 * @author Kris Zyp
 *
 */
public abstract class PersistableImpl extends IdScriptableObject implements Persistable, SecuredData,PropertyChangeListener{
	Object acl;
	public PersistableImpl() {
		
	}
	public PersistableImpl(Scriptable scope, Scriptable prototype)
    {
        super(scope, prototype);
    }
	protected ObjectId id;
	//TODO: allow for schema as well for improved performance
	ObjectId schemaParent;
	 
	public ObjectId getId() {
		if (id == null)
			return (id = new NewObjectId(this));
		return id;
	}
/*    static HashSet<Persistable> importMaps = new HashSet();
    static {
    	try {
	    	Persistable impMaps = (Persistable) DataSourceManager.getRootObject().get("importMaps");
	    	for (String key : impMaps.keySet()) {
	    		if (key.startsWith("http:"))
	    			importMaps.add((Persistable) impMaps.get(key));
	    	}
    	}
    	catch (Exception e) {
    		e.printStackTrace();
    	}
    }*/
    Persistable getReferenceParent(PersistableImpl data) {
    	if (data instanceof ReferenceAware) {
	        try {
	        	int i = 0;
	        	do {
	        		Persistable possibleParent = (Persistable)((ReferenceAwareDataSource)data.id.source).getReferrers(data.id.subObjectId).get(i++);
//	        		if (!importMaps.contains(possibleParent)) {	        			
	    				return possibleParent;
	//        		}
	        	}
	        	while (true);
	        } catch (IndexOutOfBoundsException e) { };
    	}
    	return null;
    }
    //TODO: It would be nice if this was package protected to ClientConnection
    public static Object getSlotValue(Persistable obj, String name) {
    	if (((PersistableImpl) obj).id != null && ((PersistableImpl) obj).id.source != null) {
	    	if ("name".equals(name) || "basis".equals(name) || GlobalData.VERSION_NUMBER_FIELD.equals(name)|| GlobalData.PARENT_FIELD.equals(name)) // Might consider rearanging this so we don't do this check first, we just catch the read exception and then test the name field 
	    		((PersistableImpl) obj).checkBrowseSecurity();
	    	else
	    		((PersistableImpl) obj).checkReadSecurity();
    	}
    	Object value = ScriptableObject.getSlotValue(((PersistableImpl) obj), name);
        if (value instanceof Identification) 
        	value = ((Identification)value).getTarget();
        return value;
    }
    @RemotelyExecutable
    public Object get(String name) {
    	return getProperty(this, name);
    }
    @Override
	public Object get(String name, Scriptable start) {
    	if (id != null && id.source != null) {
	    	if ("name".equals(name) || "basis".equals(name) || GlobalData.VERSION_NUMBER_FIELD.equals(name)|| GlobalData.PARENT_FIELD.equals(name)) // Might consider rearanging this so we don't do this check first, we just catch the read exception and then test the name field 
	    		checkBrowseSecurity();
	    	else
	    		checkReadSecurity();
    	}

		
        Object value = super.get(name,start);

        if (value instanceof Identification) 
        	value = ((Identification)value).getTarget();
        
        	
/*        if (value instanceof DataNotFoundInstance) {
        	if (getBasis() != null)
        		value = getBasis().get(name);
        }*/
        return value;
    }
    
    //TODO: Implement setter bypassing
    public Object set(String name, Object value) {
    	boolean persistent = false;
    	if (id != null && id.source != null && (value== null ? get(name,this) != null : !value.equals(get(name,this)))) {
    		persistent = recordPut(name,value,true);
    	}    	
    	//TODO: Should also check to see if the basis is the history basis so you can modify it but refering to it by id
        //System.err.println("PersistentObject.putImpl " + name + " = " + innerData.getClass());
    	try {
    		int index = Integer.parseInt(name);
    		super.put(index, this, value);
        	if (persistent)
        		setAttributes(index, ScriptableObject.PERSISTENT);
    	}
    	catch (NumberFormatException e) { // TODO: Maybe we should do a regex match so we dont' have to throw an exception
        	super.put(name, this, value);
        	if (persistent)
        		setAttributes(name, ScriptableObject.PERSISTENT);
    	}

		return value;
    }
    // TODO: Must override setPrototype correctly
    @Override
	public void put(int index, Scriptable start, Object obj) {
    	if (id != null && id.source != null && (obj == null ? get(index,start) != null : !obj.equals(get(index,start)))) {
    		recordPut(index,obj);
    	}
		super.put(index,start,obj);
	}
    protected void superPut(int index, Scriptable start, Object value) {
    	super.put(index, start, value);
    }
	protected void recordPut(int index, Object value) {
    	if (index == getLengthProperty())
    		checkAppendSecurity();
    	else
    		checkWriteSecurity(this);
    	if (value instanceof PersistableImpl) 
    		value= ((PersistableImpl) value).getId();
    	
       	try {
        	Object oldValue = get(index) ;
			if (oldValue == Scriptable.NOT_FOUND)
				((WritableDataSource) id.source).recordPropertyAddition(id.subObjectId, Integer.toString(index), value);
			else
				((WritableDataSource) id.source).recordPropertyChange(id.subObjectId, Integer.toString(index), value);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		firePropertyChange("" + index,value);
	}
	public List<Persistable> getSchemas() {
		List<Persistable> schemaList = new ArrayList<Persistable>(1);
    	Object schema = get("schema");
    	if (schema instanceof Persistable) 
    		schemaList.add((Persistable) schema);
		if (schemaParent != null) {
			Persistable parent = schemaParent.getTarget();
			List<Persistable> parentSchemas = parent.getSchemas();
			for (Object key : parent.getIds())
				if (parent.get(key.toString()) == this)
					for (Persistable parentSchema : parentSchemas) {
						Object possibleSchema = parentSchema.get(key instanceof String ? key.toString() : "items");
						if (!(possibleSchema instanceof Persistable)) 
							possibleSchema = parentSchema.get("*");						
						if (possibleSchema instanceof Persistable) {
							possibleSchema = ((Persistable) possibleSchema).get("type");
							if (possibleSchema instanceof Persistable) 
								schemaList.add((Persistable) possibleSchema);
						}
					}
		}
		return schemaList;
	}
	private final static NativeObject DEFAULT_FIELD = JsponicRuntime.newObject();
	/**
	 * 
	 * @param name
	 * @param obj
	 * @param alwaysPersist
	 * @return true if it should be persisted
	 */
	protected boolean enforceType(String name, Object obj,boolean alwaysPersist, boolean hadProperty) {
		if ("schema".equals(name)) {
			if (obj instanceof Persistable)
				return true;
			else
				throw new RuntimeException("Only an object can be put in a schema property");
		}
			
    	for (Persistable schema : getSchemas()){
			Object structFieldObj = ((Persistable) schema).get(name);
			if (Boolean.TRUE.equals(((Persistable) schema).get("sealed")) && !hadProperty)
				throw new RuntimeException("Can not add a property to a sealed object");
			if (structFieldObj instanceof Persistable) {
				Persistable structField = (Persistable) structFieldObj;
				if (Boolean.TRUE.equals(structField.get("transient")))
					return false;
				Object typeObj = structField.get("type");
				if (typeObj instanceof String) {
					String requiredType = (String)typeObj;
					if (!requiredType.equals("any")) {
						String type;
						if (obj instanceof String)
							type = "string";
						else if (obj instanceof Number)
							type = "number";
						else if (obj instanceof Boolean)
							type = "boolean";
						else if (obj instanceof Function)
							type = "function";
						else
							type = "object";
						if (!type.equals(requiredType))
							throw new RuntimeException("You can put a " + type + " in a " + requiredType + " property");
					}
				}
				return true;
	    	}
    	}
		if ((has(name, this) && ((getAttributes(name) & ScriptableObject.PERSISTENT) != 0)) || alwaysPersist) 
			return true;		
		else
			return false;
		
	}
    protected boolean recordPut(String name, Object obj,boolean alwaysPersist) {
    	boolean hadProperty = false;
    	try {
    		hadProperty = has(name,this);
    	}
    	catch (Exception e) {
    		e.printStackTrace();
    	}
    	if (!enforceType(name, obj, alwaysPersist,hadProperty))
    		return false;
    	checkWriteSecurity(this);
    	if (GlobalData.FUNCTION_RUN_AT_FIELD.equals(name)) {
    		if ((GlobalData.FUNCTION_RUN_AT_SERVER.equals(obj) || GlobalData.FUNCTION_RUN_AT_BOTH.equals(obj)) && !UserSecurity.userHasPermission(JavaScriptCodingPermission.instance))
    			throw new SecurityException("You do not have permission to create server side JavaScript functions");
    	}
    	Object sourceValue = obj;
    	if (obj instanceof PersistableImpl) {
    		sourceValue = ((PersistableImpl) obj).getId();
    	}
		if (GlobalData.BASIS_FIELD.equals(name)){
			Scriptable checkCircular = (Scriptable)obj;
			int i = 0;
			do {
				i++;
				if (i > 100 || checkCircular == this) {
					throw new RuntimeException("Circular prototype referencing is not allowed");
				}
				checkCircular = checkCircular.getPrototype();
			} while (checkCircular != null);
		}
    	try {
    		Identification id = getId();
			if (id instanceof ObjectId && id.source != null) {
				if (hadProperty)
					((WritableDataSource) id.source).recordPropertyChange(id.subObjectId, name, sourceValue);
				else
					((WritableDataSource) id.source).recordPropertyAddition(id.subObjectId, name, sourceValue);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
        //Object retValue = super.get(name, start);
        //TODO: move this AccessControlList
        /*if (name.equals(GlobalData.PUBLIC_PERMISSION_FIELD) ||name.equals(GlobalData.ROLE_FIELD) || name.equals(GlobalData.ROLE_PERMITS_FIELD) || name.equals(GlobalData.ACCESS_FIELD) || name.equals(GlobalData.PERMISSION_LEVEL_FIELD)) { 
        	// TODO: This is pretty sweeping cache emptying, may want to do something to improve performance here
        	resetComputedPermissions();
        	Object parent = this;
        	while (parent instanceof PersistableImpl) {
        		((PersistableImpl) parent).universalPermissionLevel = 0;
        		parent = ((PersistableImpl) parent).get(GlobalData.PARENT_FIELD);
        	}
        }*/
		
		firePropertyChange(name,obj);
		return true;
    }
    
    @Override
	public void setPrototype(Scriptable m) {
    	if (id != null && id.source != null && (m == null ? getPrototype() != null : !m.equals(getPrototype()))) 
    		recordPut(GlobalData.BASIS_FIELD,m,true);
		super.setPrototype(m);
	}
	@Override
	public void put(String name, Scriptable start, Object obj) {
    	boolean persistent = false;
    	if (id != null && id.source != null && !("length".equals(name) && this instanceof NativeArray) && (obj == null ? get(name,start) != null : !obj.equals(get(name,start)))) {// TODO: Should avoid getters
    		persistent = recordPut(name,obj,false);
    	}
    	//TODO: Should also check to see if the basis is the history basis so you can modify it but refering to it by id
        //System.err.println("PersistentObject.putImpl " + name + " = " + innerData.getClass());
    	super.put(name, start, obj);
		if (persistent)
			setAttributes(name, ScriptableObject.PERSISTENT);
    }
    @Override
    public void delete(String name) {
    	if (id != null && id.source != null) {
    		recordDelete(name);
    	}
    	super.delete(name);
    }
    private void recordDelete(String name) {
    	boolean hadProperty = false;
    	try {
    		hadProperty = has(name,this);
    	}
    	catch (Exception e) {
    		e.printStackTrace();
    	}
		checkWriteSecurity(this);
//    	if (GlobalData.FUNCTION_RUN_AT_FIELD.equals(name)) {
//    		if (GlobalData.FUNCTION_RUN_AT_SERVER.equals(obj) || GlobalData.FUNCTION_RUN_AT_BOTH.equals(obj) && UserSecurity.userHasPermission(JavaScriptCodingPermission.instance))
//    			throw new SecurityException("You do not have permission to create server side JavaScript functions");
//    	}
    	try {
    		Identification id = getId();
			if (id instanceof ObjectId && id.source != null) {
				if (hadProperty)
					((WritableDataSource) id.source).recordPropertyRemoval(id.subObjectId, name);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
        //Object retValue = super.get(name, start);
        //TODO: move this AccessControlList
        if (name.equals(GlobalData.PUBLIC_PERMISSION_FIELD) ||name.equals(GlobalData.ROLE_FIELD) || name.equals(GlobalData.ROLE_PERMITS_FIELD) || name.equals(GlobalData.ACCESS_FIELD) || name.equals(GlobalData.PERMISSION_LEVEL_FIELD)) { 
        	// TODO: This is pretty sweeping cache emptying, may want to do something to improve performance here
        	resetComputedPermissions();
        	Object parent = this;
        	while (parent instanceof PersistableImpl) {
        		((PersistableImpl) parent).universalPermissionLevel = 0;
        		parent = ((PersistableImpl) parent).get(GlobalData.PARENT_FIELD);
        	}
        }
		firePropertyChange(name,Scriptable.NOT_FOUND);

    }
    protected Object convertToIdIfNeeded(Object object) {
    	if (object instanceof Persistable) {
    		// TODO: Handle the case of data object that is not a persistent object
    		return ((PersistableImpl)object).getId();
    	}
    	return object;
    }

    private void initializeProperty(String name, Object value) {
		if (GlobalData.BASIS_FIELD.equals(name))
			super.setPrototype((Persistable) ((Identification)value).getTarget());
		else {
	    	super.put(name, this, value);
	    	try {
	    		setAttributes(name, ScriptableObject.PERSISTENT);
	    	} catch(RuntimeException e) {
	    		LogFactory.getLog(PersistableImpl.class).warn("Error changing attribute", e);
	    	}
		}
    }
/*    private static Scriptable getObjectPrototype() {
    	return ScriptableObject.getClassPrototype(GlobalData.getGlobalScope(),"Object");
	}
    private static Scriptable getArrayPrototype() {
    	return ScriptableObject.getClassPrototype(GlobalData.getGlobalScope(),"Array");
	}*/

    static void persistNewObject(final PersistableImpl newObject,NewObjectPersister newObjectPersister) {    	
    	try {
			if (newObject instanceof Function)
				newObjectPersister.initializeAsFunction((String) newObject.get(GlobalData.FUNCTION_METHOD_FIELD));
			if (newObject instanceof List)
				newObjectPersister.initializeAsList((List) newObject);
			Scriptable prototype = newObject.getPrototype();
			if (prototype != null && prototype != getClassPrototype(GlobalData.getGlobalScope(), "Object")
					&& prototype != getClassPrototype(GlobalData.getGlobalScope(), "Array")
					&& !(newObject instanceof Function))
				newObjectPersister.recordProperty(GlobalData.BASIS_FIELD, prototype);
			for (Object key : newObject.getIds())
				if (key instanceof String) {
					newObjectPersister.recordProperty((String) key, newObject.get((String) key,newObject));
					newObject.setAttributes((String) key, ScriptableObject.PERSISTENT);
				}
			newObject.acl = newObjectPersister.getAcl();
			newObjectPersister.finished();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
    }
    public static PersistableImpl createFunction(String source, String name, boolean authorizedOnServer) {
		Context context = Context.enter();
		context.setLanguageVersion(170);
		source = source.replaceAll("->",""); //TODO: Remove this
		try {
			if (authorizedOnServer)
				return (PersistableImpl) context.evaluateString(GlobalData.getGlobalScope(), "(" + source + ")", name, 1, null);
			return new BaseFunction() {
				@Override
				public Object call(Context cx, Scriptable scope,
						Scriptable thisObj, Object[] args) {
					String methodName = null;
					for (Object key : thisObj.getIds()) {
						if (key instanceof String) {
							if (thisObj.get((String)key,thisObj) == this) {
								methodName = (String) key;
								break;
							}
						}
						else {
							if (thisObj.get((String)key,thisObj) == this) {
								methodName = key.toString();
								break;
							}
							
						}
					}
					if (methodName == null)
						throw new RuntimeException("Not not make remote method call without the |this| object referring to the method being called");
					ClientConnection.getCurrentObjectResponse().sendMessage(new RPCall((Persistable)thisObj,methodName,args));
					return "Suspension";
				}
			};
		}
		catch (Exception e) {
			System.err.println(e.getMessage());
			return (PersistableImpl) context.evaluateString(GlobalData.getGlobalScope(), "(function(){throw new Error('Compilation error in this function')})", name, 1, null);
		}

    }
    private void setId(ObjectId id) {
    	this.id = id;
    	if (acl == null) {
    		if (id != null && id.source instanceof WritableDataSource)
    			acl = SystemAcls.DEFAULT_ACL; 
    		else
    			acl = SystemAcls.IMMUTABLE_ACL; 
    	}
    }
    private static class PersistentInitializer implements PersistableInitializer {


		public void setSchema(Persistable schema) {
			setProperty("schema", schema);
		}
		public void setCacheLevel(long time) {
			// TODO Auto-generated method stub
		}
		PersistableImpl newObject;
    	ObjectId id;
    	PersistentInitializer() {    		
    	}
    	PersistentInitializer(ObjectId id) {
    		this.id = id;    		
    		if (id.targetRef != null)
    			newObject = (PersistableImpl) id.targetRef.get(); // in case it is already initialized
    	}
		public void initializeObject(PersistableImpl object) {
			if (newObject != null)
				throw new RuntimeException("initializeObject must be called before any of the other initializer methods");
			newObject = object;
			newObject.setId(id);
		}
		public void initializeFunction(String source, boolean authorizedOnServer) {
			if (newObject != null)
				throw new RuntimeException("initializeFunction must be called before any of the other initializer methods");
			Context context = Context.enter();
			newObject = createFunction(source,id.toString(),authorizedOnServer);
			newObject.setId(id);
			//Context.exit();
		}
		public void initializeByPrototype(ObjectId prototypeId) {
			if (newObject == null) {
				newObject = JsponicRuntime.newObject();
				newObject.setId(id);
			/*	Persistable prototype = prototypeId.getTarget();
				Class clazz = prototype.getClass();
				try {
					if (prototype instanceof List) {
						newObject = (PersistableImpl) clazz.getConstructor(new Class[]{long.class}).newInstance(new Object[]{new Long(0)});
						((PersistentList)newObject).isPersistingList = true;
					}
					else
						newObject = (PersistableImpl) clazz.newInstance();
				} catch (Exception e) {
					throw new RuntimeException(e);
				}*/
			}
			newObject.initializeProperty(GlobalData.BASIS_FIELD,prototypeId);
		}
		public void initializeList(Iterator iterator) {
			if (newObject == null) {
				newObject = JsponicRuntime.newArray();
				newObject.setId(id);
			}
			List tempList = new ArrayList();
			while (iterator.hasNext()) {
				tempList.add(iterator.next()); //handleChildObjectInitialize(iterator.next())
			}
			((PersistentList)newObject).initializeArray(tempList.toArray());
		}
/*		private Object handleChildObjectInitialize(Object value) {
			if (value instanceof ObjectWithInitialization) {
				ObjectId childId = ((ObjectWithInitialization)value).getId(); 
				try {
					DataObjectInitializer childInitializer = new PersistentInitializer(childId);
					((ObjectWithInitialization)value).mapObject(childInitializer);
					childId.targetRef = new SoftReference(childInitializer.getInitializingObject());
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				value = childId;
			}
			return value;
		}*/
		public void setProperty(String name, Object value) {
			try {
				if (newObject == null) {
					newObject = JsponicRuntime.newObject();
					newObject.setId(id);
				}
				newObject.initializeProperty(name,value);//handleChildObjectInitialize(value)				
			} catch (RuntimeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
    	public void setAcl(Acl acl) {
			if (newObject == null) {
				newObject = JsponicRuntime.newObject();
				newObject.id = id;
			}
			newObject.acl = acl;
		}
    	public void setPersistentAcl(ObjectId aclId) {
			if (newObject == null) {
				newObject = JsponicRuntime.newObject();
				newObject.id = id;
			}
    		if (aclId == id && !(newObject instanceof Acl))
    			throw new RuntimeException("Can not set an acl id to be the same as the object id unless the object is an acl");
			newObject.acl = aclId;
		}
		public void setParent(ObjectId objectToInheritFrom) {
			if (newObject == null) {
				newObject = JsponicRuntime.newObject();
				newObject.id = id;
			}
    		if (objectToInheritFrom == id && !(newObject instanceof Acl))
    			throw new RuntimeException("Can not set an acl id to be the same as the object id unless the object is an acl");
			newObject.acl = objectToInheritFrom;
			newObject.schemaParent = objectToInheritFrom;
		}
		public Persistable getInitializingObject() {
			if (newObject == null)
				newObject = JsponicRuntime.newObject();
			newObject.setId(id);
			return newObject;
		}
		public void finished() {
			
		}
    };
    /**
     * @see DataSourceHelper.initializeObject()
     * @param id
     * @return
     */
    static PersistableInitializer initializeObject() {
    	return new PersistentInitializer();
    	
    }
    static Map<ObjectId,PersistableInitializer> currentlyPersisting = new HashMap<ObjectId,PersistableInitializer>();
    /**
     * @see DataSourceHelper.initializeObject(id)
     * @param id
     * @return
     */
    static PersistableInitializer initializeObject(final ObjectId id) {
    	synchronized(currentlyPersisting) {
    		PersistableInitializer initializer = currentlyPersisting.get(id); // if it is currently being persisted, we want to reuse it
    		if (initializer != null)
    			return initializer;
			initializer = new PersistentInitializer(id) {
	    		@Override
				public void finished() {
	    			id.targetRef = new SoftReference<Persistable>(getInitializingObject());
	    	    	synchronized(currentlyPersisting) {
	    	    		currentlyPersisting.remove(id);
	    	    	}
	    		}
	    	};
	    	currentlyPersisting.put(id,initializer);
	    	return initializer;
    	}
    }
    static PersistableImpl mapPersistent(final ObjectId id) {
    	PersistableInitializer initializer;
	    	initializer = new PersistentInitializer(id);
    	synchronized(currentlyPersisting) {
	    	currentlyPersisting.put(id, initializer);
    	}
    	try {
        	if (id instanceof Query)
        		((QueriableDataSource) id.source).mapQuery(initializer, (Query)id);
        	else
        		id.source.mapObject(initializer,id.subObjectId);
		} catch (Exception e) {
			e.printStackTrace();
			PersistableImpl newObject = (PersistableImpl) initializer.getInitializingObject();
			newObject.id=null; // do this so we can set the error without trying to persist it
			newObject.set("error", e.toString());
			newObject.id = id;
		}
		PersistableImpl newObject = (PersistableImpl) initializer.getInitializingObject();
    	if (id.source instanceof ChangeableData)
    		((ChangeableData) id.source).addPropertyChangeListener(newObject);
    	synchronized(currentlyPersisting) {
	    	currentlyPersisting.remove(id);
    	}

        return newObject; 
    }

	static Map<String, Map<PropertyChangeListener,Object>> propertyChangeListeners = new HashMap<String, Map<PropertyChangeListener,Object>>(100);
	static int listenerAdditions = 0;
	private static void expungeStaleListenerMaps() {
		synchronized (propertyChangeListeners) {
			List<String> forRemoval = new ArrayList<String>();
			for (Entry<String, Map<PropertyChangeListener,Object>> entry : propertyChangeListeners.entrySet()) {
				if (entry.getValue().size()==0)
					forRemoval.add(entry.getKey());
			}
			for (String id : forRemoval)
				propertyChangeListeners.remove(id);
		}
	}
    public static void addListener(Persistable obj, PropertyChangeListener listener) {
		if (listenerAdditions++ > 10) {
			listenerAdditions= 0;
			expungeStaleListenerMaps();
		}
		String id = obj.getId().toString();
		synchronized (propertyChangeListeners) {
	    	Map<PropertyChangeListener,Object> set = propertyChangeListeners.get(id);
	    	if (set==null) {
	    		set = new WeakHashMap<PropertyChangeListener,Object>(2);
	    		propertyChangeListeners.put(id,set);
	    	}
	    	set.put(listener,PersistableImpl.class); // the value doesn't matter
		}
    }
    public static void removeListener(String id, PropertyChangeListener listener) {
		synchronized (propertyChangeListeners) {
	    	Map<PropertyChangeListener,Object> set = propertyChangeListeners.get(id);
	    	if (set==null)
	    		return; // nothing to remove
	    	set.remove(listener);
		}
    }
    public void addPropertyChangeListener(PropertyChangeListener listener) {
    	addListener(this,listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
    	removeListener(getId().toString(),listener);
    }
    void firePropertyChange(String propertyName,Object newValue) {
    	PropertyChangeEvent evt = new PropertyChangeEvent(this,propertyName,null,newValue);
    	Map<PropertyChangeListener,Object> listeners = propertyChangeListeners.get(getId().toString());
    	propertyChangeListeners.remove(getId().toString()); // restart the values, the need to reregister if they need to
    	if (listeners != null)
    		for (PropertyChangeListener listener : listeners.keySet()) 
    			listener.propertyChange(evt);    	
    }
     
	public void propertyChange(java.beans.PropertyChangeEvent evt) {
        try {
            System.err.println("PersistentObject.propertyChange " + evt.getSource() + " " + evt.getPropertyName());
            // if it is not permission related we can ignore it, but this is not correct, because we must figure in parents that don't have the permission set
            //if (evt.getSource().equals(getId().toString()) && !(evt.getPropertyName().equals(GlobalData.PERMISSION_FIELD)))
            	//return;
            
            //reset the permissions
            universalPermissionLevel = 0;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
     
     
    /* Security part of class */
	/*public PermissionLevel getAccessLevel() {
		return checkSecurity(this);
	}*/
	private Object superGet(String name) {
		Object value = super.get(name, this);
        if (value instanceof Identification) 
        	value = ((Identification)value).getTarget();
        return value;

	}
	protected Object superGet(int index) {
		Object value = super.get(index, this);
        if (value instanceof Identification) 
        	value = ((Identification)value).getTarget();
        return value;
	}
    int universalPermissionLevel = -1; 
    PermissionLevel checkSecurity(PersistableImpl data) {
    	Acl acl = getAcl();	
        if (acl == null) // indicates it is transient
        	return PermissionLevel.WRITE_LEVEL;
    	// TODO: Pass in the permission level required and then do logic short-circuits if the permission level is found
        final Principal user = UserSecurity.currentUser();
        if (user == null) {
        	throw new RuntimeException("no user is registered for this thread"); 
        	//permission = PermissionLevel.readAccess;
        	//return;
        } 
    	if (user instanceof PriviledgedUser)
    		return PermissionLevel.WRITE_LEVEL;
    	if (universalPermissionLevel == -1) {
            Enumeration<Permission> permissions = getAcl().getPermissions(UserSecurity.getPublicUser());
            while (permissions.hasMoreElements()) {
            	Permission perm = permissions.nextElement();
            	if (perm instanceof PermissionLevel)
            		universalPermissionLevel = ((PermissionLevel) perm).level;
            }
            if (universalPermissionLevel == -1)
            	universalPermissionLevel = 0;
    	}
/*        if (permission != null) {
        	return permission; 
        }*/
        //if (data instanceof TransientDataObjectList)
        	//return PermissionLevel.fullAccess;
        //if (acl == null)
           // return PermissionLevel.WRITE_LEVEL;//throw new SecurityException(this, SecurityException.ACCESS_PERMISSION_LEVEL);
        Enumeration<Permission> permissions = acl.getPermissions(user);
        while (permissions.hasMoreElements()) {
        	Permission perm = permissions.nextElement();
        	if (perm instanceof PermissionLevel)
        		return (PermissionLevel) perm;
        }
        return PermissionLevel.NONE_LEVEL;
/*        permission = PersistentAcl.computeAccessLevel((PersistentList) acl,new TargetedComputation() {
			public User getUser() {
				return user;
			}

			public void setUniversalPermissionLevel(int level) {
				universalPermissionLevel = level;				
			}
        	
        });
        user.computedPermissions.put(data,permission);*/
        //return permission;
    }
	public static void resetComputedPermissions() {
		computedPermissions = new WeakHashMap<Principal, Map<Persistable,PermissionLevel>>();
	}
    protected static Map<Principal,Map<Persistable,PermissionLevel>> computedPermissions = new WeakHashMap<Principal, Map<Persistable,PermissionLevel>>();
    static class PermissionChangeListener implements PropertyChangeListener {
		public void propertyChange(PropertyChangeEvent evt) {
			resetComputedPermissions();
		}
    }
    static PermissionChangeListener permissionChangeListener = new PermissionChangeListener(); 
    /**
     * This is used by PersistentAcl and I am planning on hiding it
     * @param permissionObject
     * @param targetOfComputation
     * @return
     */
    @Deprecated
    public static PermissionLevel computeAccessLevel(PersistableImpl permissionObject, Principal user) {
//    	User user = targetOfComputation.getUser();
        //System.err.println("Permission Object: " + permissionObject);
    	Map<Persistable,PermissionLevel> userPermissions = computedPermissions.get(user);
    	if (userPermissions == null) {
    		userPermissions = new WeakHashMap<Persistable, PermissionLevel>();
    		computedPermissions.put(user,userPermissions);
    	}
        PermissionLevel permission = userPermissions.get(permissionObject);            
        if (permission != null)
            return permission;
        //if (permissionObject == SystemAcls.IMMUTABLE_ACL)
        	//permission = PermissionLevel.READ_LEVEL;
        else {
	        //DataObjectList rolePermits = (DataObjectList) permissionObject.get(GlobalData.ROLE_PERMITS_FIELD);
        	permissionObject.addPropertyChangeListener(permissionChangeListener);
	        try {
	        	Persistable publicPermissionField = (Persistable) ((PersistableImpl) permissionObject).superGet(GlobalData.PUBLIC_PERMISSION_FIELD);
	        	if (publicPermissionField != null)
	        		permission = PermissionLevel.values()[((Long) ((PersistableImpl) ((PersistableImpl) permissionObject).superGet(GlobalData.PUBLIC_PERMISSION_FIELD)).superGet(GlobalData.LEVEL_FIELD)).intValue()];
	        	else 
	        		permission = PermissionLevel.NONE_LEVEL;
	//    		targetOfComputation.setUniversalPermissionLevel(permission.level);
	        }
	        catch (ClassCastException e) {
	        	//System.err.println("No public permission was set " + e.getMessage());
	        }
	        boolean foundPermission = true;
	        int size = permissionObject instanceof PersistentList ? ((PersistentList) permissionObject).size() : -1;
	        for (int i = 0; i < size || size == -1; i++) { // allow for non-list inheriting lists
	        	Object aclEntryObject = ((PersistableImpl) permissionObject).superGet(i);
	        	
	        	if (!(aclEntryObject instanceof PersistableImpl)) {
	        		if (aclEntryObject == Scriptable.NOT_FOUND)
	        			break;
	        		throw new RuntimeException("acl entry not valid");
	        	}
	            PersistableImpl aclEntry = (PersistableImpl) aclEntryObject;
	            aclEntry.addPropertyChangeListener(permissionChangeListener);

	            Object role = aclEntry.superGet(GlobalData.ROLE_FIELD);
	            //User.recalculateGroupMembership((DataObject) role);
	/*            if (role.equals(User.EVERYONE)) {
	                foundPermission = true;
	                int permitLevel = ((Long) ((DataObject) permit.get(GlobalData.PERMISSION_LEVEL_FIELD)).get(GlobalData.LEVEL_FIELD)).intValue();
	                universalPermissionLevel = permitLevel;
	                if (permitLevel > permission.level)
	                	permission.level = permitLevel;
	            }
	            else 
	*/            if (role == user || (role instanceof Group && ((Group)role).isMember(user))) {
	                foundPermission = true;
	                int permitLevel = ((Long) ((PersistableImpl) aclEntry.superGet(GlobalData.PERMISSION_LEVEL_FIELD)).superGet(GlobalData.LEVEL_FIELD)).intValue();
	                if (permission == null || permitLevel > permission.level)
	                	permission = PermissionLevel.values()[permitLevel];
	            }
	        }
	        //System.err.println("permission.level " + permission.level);
	        //if (!foundPermission)  {
	            if (permission != PermissionLevel.WRITE_LEVEL && UserSecurity.getSupervisorGroup().isMember(user)){ // if the user is the supervisor they can access anything
	            	foundPermission = true;
	            	permission = PermissionLevel.WRITE_LEVEL;
	            }
	            //if (permission.level < 2) //TODO: remove this
	            	//throw new RuntimeException("permission set to browse");
	            /*else {
	            	System.err.println("The user " + User.getLogin() + "did not match any permissions in " + permissionObject);
	            	throw new SecurityException(this,SecurityException.BROWSE);
	            }
	        }*/
	        }
        if (permission == null)
        	permission = PermissionLevel.LIMITED_LEVEL;
        userPermissions.put(permissionObject,permission);

        return permission;
    }

    
    public int getAccessLevel() {
    	return checkSecurity(this).level;
    }
    private void checkBrowseSecurity() { 
        if (universalPermissionLevel > 0)
        	return;        
        if (!checkSecurity(this).canBrowse()) {
            //System.err.println("permission object is " + checkSecurity(this));
            throw new ObjectAccessDeniedException(this, ObjectAccessDeniedException.BROWSE); 
        }
    }
    protected void checkReadSecurity() { 
        if (universalPermissionLevel > 1)
        	return;
        if (!checkSecurity(this).canRead()) {
            //System.err.println("permission objec t is " + checkSecurity(this));
            throw new ObjectAccessDeniedException(this, ObjectAccessDeniedException.READ); 
        }
    }
    protected void checkAppendSecurity() { 
        if (universalPermissionLevel > 3)
        	return;
        if (!checkSecurity(this).canAppend()) {
            //System.err.println("permission object is " + checkSecurity(this));
            throw new ObjectAccessDeniedException(this, ObjectAccessDeniedException.APPEND); 
        }
    	if (Boolean.TRUE.equals(super.get(GlobalData.HISTORY_IDENTIFIER_FIELD,this)))
    		throw new RuntimeException("You can not modify history");
    }
    protected void checkWriteSecurity(PersistableImpl data) { 
        if (!checkSecurity(data).canWrite()) 
            throw new ObjectAccessDeniedException(this, ObjectAccessDeniedException.WRITE);
    	if (Boolean.TRUE.equals(data.get(GlobalData.HISTORY_IDENTIFIER_FIELD)))
    		throw new RuntimeException("You can not modify history");
    }

    protected List getPermissionObject(PersistableImpl data) {
        Object permissionObject = data.superGet(GlobalData.ACCESS_FIELD);
        int i = 0;
        while (!(permissionObject instanceof Persistable)) {
            i++;
            if (i > 1000)
            	throw new RuntimeException("The parent reference chain was too long (indicating a circular parent reference)");
            //System.err.println("No permission object for " + data);
            try {
                data = (PersistableImpl) ((ObjectId) data.superGet(GlobalData.PARENT_FIELD)).getTarget();
            }
            catch (ClassCastException e) {
            	if (!(data instanceof PersistableList))
            		return null;
            	data = (PersistableImpl) getReferenceParent(data);
            	if (data == null)
            		return null;
            }
            catch (NullPointerException e) {
            	return null;
            }
            if (data == null)
                return null;//throw new RuntimeException("Unable to find parent for " + oldData + oldData.getClass());
            permissionObject = ((ObjectId) data.superGet(GlobalData.ACCESS_FIELD)).getTarget();
        }
/*        if (!(data instanceof DataObject))
            throw new RuntimeException("Permission Object must be a source object");*/
        return (PersistableList) permissionObject;
    }
    boolean isSourceEditable() {
        return checkSecurity(this).canWrite();
    }

	
	
	public Object get(int index) {
		return get(index,this);
	}
	@Override
	public Object get(int index, Scriptable start) {
    	if (id != null && id.source != null) 
    		checkReadSecurity();
    	Object value = super.get(index,this);
        if (value instanceof Identification) 
        	value = ((Identification)value).getTarget();
        return value;
    }


/*    public Entity createNew(Map map) {
        System.err.println("PersistentList.createNew With map" + getClass() + " inner is " + data.getClass());
/*        if (data() == null) 
            data = new TransientDataObjectList(); *
        checkAppendSecurity();
        for (Object key : map.keySet()) {
            map.put(key,retrieveInnerObject(map.get(key)));
        }
        return (Entity) wrapper("",data().createNew(map));
    }*/
     public Date getLastModified() {
		Object historyObject = get(GlobalData.HISTORY_FIELD);
		if (historyObject instanceof PersistableList) {
			PersistableList history = (PersistableList) historyObject;
			return (Date) ((Persistable) history.get(history.size()-1)).get("modified"); 
		}
		return new Date(0);
	}
	public Set<String> keySet() {
		Object[] ids = getIds();
		Set<String> keySet = new HashSet();
		for (Object key : ids) 
			if (!(key instanceof String) || id == null || id.source == null || (getAttributes((String)key) & ScriptableObject.PERSISTENT) != 0)
				keySet.add(key.toString());
		return keySet;
	}
    @Override
	public String toString() {
    	return getId() + "@" + getClassName();
    }
	public Acl getAcl() {
		if (acl instanceof ObjectId) {
			Persistable target = ((ObjectId)acl).getTarget();
			if (target instanceof Acl)
				return (Acl) target;
			else
				return target.getAcl();
		}
		return (Acl) acl;
		
	}
	public void setAcl(Object acl) {
		this.acl = acl;
	}
    private long getLengthProperty() {
        // These will both give numeric lengths within Uint32 range.
        if (this instanceof NativeArray) 
            return ((NativeArray)this).getLength();
        Object length = ScriptRuntime.getObjectProp(this, "length", Context.enter());
        if (length instanceof Number)
        	return ScriptRuntime.toUint32(length);
        return 0;
    }
     
}
